package avicit.bdp.dgs.qa.utils.redis;

import avicit.bdp.common.utils.SpringApplicationContext;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.util.Objects;

/**
 * @金航数码科技有限责任公司
 * @作者：developer
 * @邮箱：developer@avic-digital.com
 * @创建时间： 2020-12-09
 * @类说明：RedisLockUtils
 * @修改记录：
 * @注意事项：
 * @主要功能：Redis分布式锁
 */
public class RedisLock {
    /**
     * redis分布式锁前缀
     */
    public static final String LOCK_PREFIX = "dgs_qa_redis_lock_";

    /**
     * redis分布式锁过期时间
     */
    public static final int LOCK_EXPIRE = 1000;

    private static RedisLock instance = new RedisLock();

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 私有构造函数
     */
    private RedisLock() {
        this.redisTemplate =  (RedisTemplate) SpringApplicationContext.getBean("redisTemplate");
    }

    /**
     * 单例模式
     * @return
     */
    public static RedisLock getInstance() {
        return instance;
    }

    /**
     * 分布式锁，获取锁
     *
     * @param key
     */
    public void dlmLock(String key) {
        int i = 0;
        boolean lock = RedisLock.getInstance().lock(key);
        while (!lock && i++ < 100) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock = RedisLock.getInstance().lock(key);
        }
    }

    /**
     * 分布式锁，释放锁
     *
     * @param key
     */
    public void dlmUnlock(String key) {
        RedisLock.getInstance().unlock(key);
    }

    /**
     * 获取锁
     * @param key
     * @return
     */
    public boolean lock(String key) {
        String lock = LOCK_PREFIX + key;

        return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
            long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
            Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());

            if (acquire) {
                return true;
            }

            byte[] value = connection.get(lock.getBytes());
            if (Objects.nonNull(value) && value.length > 0) {

                long expireTime = Long.parseLong(new String(value));

                if (expireTime < System.currentTimeMillis()) {
                    // in case the lock is expired
                    byte[] oldValue = connection.getSet(lock.getBytes(),
                            String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
                    // avoid dead lock
                    return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                }
            }

            return false;
        });
    }

    /**
     * 释放锁
     * @param key
     */
    public void unlock(String key) {
        redisTemplate.execute(new RedisCallback<Void>() {
            public Void doInRedis(RedisConnection jedis) throws DataAccessException {
                jedis.del(new byte[][]{encode(key)});
                return null;
            }
        });
    }

    /**
     * 加密
     * @param str
     * @return
     */
    public static byte[] encode(String str) {
        try {
            if (str == null || str.trim().length() == 0) {
                return new byte[0];
            }

            return str.getBytes("UTF-8");
        } catch (UnsupportedEncodingException unsupportedEncodingException) {
            return new byte[0];
        }
    }
}
